-- Listing 4.20
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity fifo is
  generic(
    B           :     natural := 8;     -- number of bits
    W           :     natural := 4      -- number of address bits
    );
  port(
    clk, reset  : in  std_logic;
    rd, wr      : in  std_logic;
    w_data      : in  std_logic_vector (B-1 downto 0);
    empty, full : out std_logic;
    r_data      : out std_logic_vector (B-1 downto 0)
    );
end fifo;

architecture arch of fifo is
  type reg_file_type is array (2**W-1 downto 0) of
    std_logic_vector(B-1 downto 0);
  signal array_reg : reg_file_type;
  signal w_ptr_reg, w_ptr_next, w_ptr_succ :
    std_logic_vector(W-1 downto 0);
  signal r_ptr_reg, r_ptr_next, r_ptr_succ :
    std_logic_vector(W-1 downto 0);
  signal full_reg, empty_reg, full_next, empty_next :
    std_logic;
  signal wr_op     : std_logic_vector(1 downto 0);
  signal wr_en     : std_logic;
begin
  --=================================================
  -- register file
  --=================================================
  process(clk, reset)
  begin
    if (reset = '1') then
      array_reg <= (others => (others => '0'));
    elsif (clk'event and clk = '1') then
      if wr_en = '1' then
        array_reg(to_integer(unsigned(w_ptr_reg)))
                <= w_data;
      end if;
    end if;
  end process;
  -- read port
  r_data        <= array_reg(to_integer(unsigned(r_ptr_reg)));
  -- write enabled only when FIFO is not full
  wr_en         <= wr and (not full_reg);

  --=================================================
  -- fifo control logic
  --=================================================
  -- register for read and write pointers
  process(clk, reset)
  begin
    if (reset = '1') then
      w_ptr_reg <= (others => '0');
      r_ptr_reg <= (others => '0');
      full_reg  <= '0';
      empty_reg <= '1';
    elsif (clk'event and clk = '1') then
      w_ptr_reg <= w_ptr_next;
      r_ptr_reg <= r_ptr_next;
      full_reg  <= full_next;
      empty_reg <= empty_next;
    end if;
  end process;

  -- successive pointer values
  w_ptr_succ <= std_logic_vector(unsigned(w_ptr_reg)+1);
  r_ptr_succ <= std_logic_vector(unsigned(r_ptr_reg)+1);

  -- next-state logic for read and write pointers
  wr_op                <= wr & rd;
  process(w_ptr_reg, w_ptr_succ, r_ptr_reg, r_ptr_succ, wr_op,
          empty_reg, full_reg)
  begin
    w_ptr_next         <= w_ptr_reg;
    r_ptr_next         <= r_ptr_reg;
    full_next          <= full_reg;
    empty_next         <= empty_reg;
    case wr_op is
      when "00"   =>                    -- no op
      when "01"   =>                    -- read
        if (empty_reg /= '1') then      -- not empty
          r_ptr_next   <= r_ptr_succ;
          full_next    <= '0';
          if (r_ptr_succ = w_ptr_reg) then
            empty_next <= '1';
          end if;
        end if;
      when "10"   =>                    -- write
        if (full_reg /= '1') then       -- not full
          w_ptr_next   <= w_ptr_succ;
          empty_next   <= '0';
          if (w_ptr_succ = r_ptr_reg) then
            full_next  <= '1';
          end if;
        end if;
      when others =>                    -- write/read;
        w_ptr_next     <= w_ptr_succ;
        r_ptr_next     <= r_ptr_succ;
    end case;
  end process;
  -- output
  full                 <= full_reg;
  empty                <= empty_reg;
end arch;
